//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#ifndef __ELASTOS_FD_REGISTER_H__
#define __ELASTOS_FD_REGISTER_H__

EXTERN void DzDelay(Microsecond32 uMicroseconds);

static uint8_t u8Position;
static uint8_t u8Action;

/*action type define*/
#define ACTION_NOACTION     0x00
#define ACTION_SEEK         0x01
#define ACTION_RECA         0x02
#define ACTION_READ         0x03
#define ACTION_WRITE        0x04
#define ACTION_FORMAT       0x08
#define ACTION_DOR          0x10
#define ACTION_RESET        0xFF

#define WAIT_MAX_COUNT      1000000000
#define FLOPPYDRIVER_IRQ    0x06
#define FLOPPYDRIVER_DMA    0x02
#define FLOPPY_HEADS        0x02
#define FLOPPY_TRACKS       0x50
#define FLOPPY_SECTORSPERTRACKS 0x12

//Primary Address Secondary Address Write (W) Read (R)
#define FD_BASE_ADDRESS                 0x3f0
#define FD_STATUS_REGISTER_A            0x3f0
#define FD_STATUS_REGISTER_B            0x3f1
#define FD_DIGITAL_OUTPUT_REGISTER      0x3f2   //DOR write only
#define FD_MAIN_STATUS_REGISTER         0x3f4   //Read
#define FD_DATA_RATE_SELECT_REGISTER    0x3f4
#define FD_DATA_REGISTER                0x3f5
#define FD_DIGITAL_INPUT_REGISTER       0x3f7   //DIR read only
#define FD_CONFIGURATION_CONTROL_REGISTER 0x3f7

//FD_DIGITAL_OUTPUT_REGISTER
#define MOTD        __8BIT(7)   //  select motor d
#define MOTC        __8BIT(6)   //  select motor c
#define MOTB        __8BIT(5)   //  select motor b
#define MOTA        __8BIT(4)   //  select motor a
#define ENABLE_DMA  __8BIT(3)   //  enable dma and irq
#define NOT_REST    __8BIT(2)   //  0 means reset
#define DRV1        __8BIT(1)   //  select drive abcd
#define DRV0        __8BIT(0)   //  select drive abcd
/* example:
 * mov al, 01ch     1ch = 00011100  motor a, enable dma,
 * no reset, select drive a
 * mov dx, 03f2h
 * out dx, al
*/

//FD_MAIN_STATUS_REGISTER
#define MAIN_REQ        __8BIT(7)   //  main request
                                    //  1=data reg ready 0=data reg not ready
#define DATA_IO         __8BIT(6)   //  data io, 1=disk->cpu, 0=cpu->disk
#define NO_DMA          __8BIT(5)   //  0=in dma mode, 1=not in dma mode
#define DEVICE_BUSY     __8BIT(4)   //  device busy, 1=active, 0=not active
#define ACTD            __8BIT(3)   //
#define ACTC            __8BIT(2)   //  a,b,c,d @ position mode
#define ACTB            __8BIT(1)   //  1=active, 0=not active
#define ACTA            __8BIT(0)   //
/* example:
 * mov dx,03f4h
 * in al,dx    to analyse the al register.
*/

//FD_DIGITAL_INPUT_REGISTER
#define DIS_CHAN        __8BIT(7)   //  disk changed 1=changed, 0=not changed
#define REV6            __8BIT(6)   //  at:rev  ps/2:1  ps/2model30:0
#define REV5            __8BIT(5)   //  at:rev  ps/2:1  ps/2model30:0
#define REV4            __8BIT(4)   //  at:rev  ps/2:1  ps/2model30:0
#define DMA_IN_DOR      __8BIT(3)   //  at:rev  ps/2:1  ps/2model30:dma in dor
#define NOPR            __8BIT(2)   //  at:rev  ps/2:RAT1
                                    //  ps/2model30:Value of NOPR bit
                                    //  in Control Configuration Register
#define RAT1            __8BIT(1)   //  at:rev  ps/2:RAT0   ps/2model30:RAT1
#define RAT0            __8BIT(0)   //  at:rev  ps/2:High-density Rate
                                    //  1 = data rate 250kbits/s or 300 kbits/s
                                    //  0 = data rate 1Mbits/s or 500 kbits/s
                                    //  ps/2model30:RAT0

////FD_CONFIGURATION_CONTROL_REGISTER
//#define NOPR            __8BIT(2)   //    the same as
//#define RAT1            __8BIT(1)   //    FD_DIGITAL_INPUT_REGISTER
//#define RAT0            __8BIT(0)   //

//FD_STATUS_REGISTER_A&FD_STATUS_REGISTER_B
//FD_STATUS_REGISTER_A  PS/2
#define INTP            __8BIT(7)   //  interrupt pending
#define DRV_2           __8BIT(6)   //  0=2 drivers 1=1 drivers
#define STEP_PULSE      __8BIT(5)   //  stepper pulse,0=no pulse,1=pulse
                                    //  submitted
#define TRK0            __8BIT(4)   //  1=head above track0 0=not above track0
#define HDSL            __8BIT(3)   //  head select, 0=head 0,1=head 1
#define INDX            __8BIT(2)   //  index mark 0=not detected,1=detected
#define WRITEPROT       __8BIT(1)   //  write protection,0=protection,1=not
#define DIRHEAD         __8BIT(0)   //  direction of head,0=outwards 1=inwards

//FD_STATUS_REGISTER_B PS/2
#define DRIVE0          __8BIT(5)   //  0=drive other than 0,1=drive 0
#define WDAT            __8BIT(4)   //  write data,0=no data to written,1=can
#define RDAT            __8BIT(3)   //  read data,0=no data to read,1=can
#define WRITEENABLE     __8BIT(2)   //  0=to read,1=to write
#define MOT1            __8BIT(1)   //  0=motor off,1=motor on  (motor1)
#define MOT0            __8BIT(0)   //  0=motor off,1=motor on  (motor0)

uint8_t get_main_status();
bool is_mrq();

#define FD_MAIN_STATUS_DELAY  DzDelay(500);
#define FD_CMD_DELAY    DzDelay(25000);
#define FD_MOTOR_DELAY  DzDelay(500000);

#define CMD_FD_RECALIBRATE  0x07
#define CMD_FD_SEEK         0x0F
#define CMD_FD_SENSEI       0x08

/*not...*/
static bool bReca = false;

INLINE void recalibrate_seek(bool bIni)
{
    uint8_t dno = 0x00;
    uint8_t hno = 0x00;

    bReca = true;

    kprintf("The main status is 0x%x before recalibrate.\n",
            Inb(FD_MAIN_STATUS_REGISTER));

    FD_CMD_DELAY;

    u8Action = ACTION_RECA;
    if (bIni) {
        Outb(FD_DATA_REGISTER, CMD_FD_SEEK);
        FD_CMD_DELAY;
        Outb(FD_DATA_REGISTER, dno | hno);
        FD_CMD_DELAY;
        Outb(FD_DATA_REGISTER, 0x01);
    }
    else {
        Outb(FD_DATA_REGISTER, CMD_FD_RECALIBRATE);
        FD_CMD_DELAY;
        Outb(FD_DATA_REGISTER, dno);
    }

    FD_CMD_DELAY;
}

/*  This method read status after interrupt.
 *  It return the first result 8bits but not second one.
 */
INLINE uint8_t read_interrupt_status()
{
    uint8_t u8Status = 0x00;

    Outb(FD_DATA_REGISTER, CMD_FD_SENSEI);
    FD_MAIN_STATUS_DELAY;
    u8Status = Inb(FD_DATA_REGISTER);
    FD_MAIN_STATUS_DELAY;
//    kprintf("ST0 is 0x%x\n", u8Status);

    u8Position = Inb(FD_DATA_REGISTER);

//    kprintf("located track:%d\n", u8Position);

    return u8Status;
}

#define CMD_FD_SPECIFY 0x03

/*  This method specify the feature of fd's trasfer.
 *  It does not generate interrupts.
 */
INLINE void fd_specify(uint8_t srt_hut, uint8_t hlt_nd)
{
    Outb(FD_DATA_REGISTER, CMD_FD_SPECIFY);
    FD_CMD_DELAY;
    Outb(FD_DATA_REGISTER, srt_hut);
    FD_CMD_DELAY;
    Outb(FD_DATA_REGISTER, hlt_nd);
    FD_CMD_DELAY;
}

//Status byte 0
#define ST0_INTR        (__8BIT(7)|__8BIT(6))
                                    //  00: cmd normal end, 01:cmd abnormal end
                                    //  10: cmd invalid, 11: fd status change.
#define ST0_SE          __8BIT(5)   //  Seek end.
#define ST0_ECE         __8BIT(4)   //  Zero track check error
#define ST0_NR          __8BIT(3)   //  driver not ready
#define ST0_HA          __8BIT(2)   //  Head address when interrupt
#define ST0_DS          (__8BIT(1)|__8BIT(0))
                                    //  Driver no. when interrupt

INLINE uint8_t get_main_status()
{
    return Inb(FD_MAIN_STATUS_REGISTER);
}

INLINE bool is_mrq()
{
    uint_t uCount = 0;
    while (!(get_main_status()&MAIN_REQ)) {
        uCount++;
        if (uCount > WAIT_MAX_COUNT) return false;
    }
    return true;
}

INLINE bool reset_floppy()
{
    uint8_t uDor = MOTA | ENABLE_DMA | NOT_REST | 0x00;

    u8Position = 0xFF;
    u8Action = ACTION_DOR;

    Outb(FD_DIGITAL_OUTPUT_REGISTER, uDor & ~0x04);

    kprintf("Reset-step1\n");
    FD_MOTOR_DELAY;
    FD_MOTOR_DELAY;

    u8Action = ACTION_DOR;
    Outb(FD_DIGITAL_OUTPUT_REGISTER, uDor);
    FD_MOTOR_DELAY;
    kprintf("Reset-step2\n");
    return true;
}

INLINE void open_motor()
{
    //output DOR to enable the motor A
    uint8_t uDor = MOTA | ENABLE_DMA | NOT_REST | 0x00;
    Outb(FD_DIGITAL_OUTPUT_REGISTER, uDor);
    FD_MOTOR_DELAY;
}
INLINE void close_motor()
{
    uint8_t uDor = ENABLE_DMA | NOT_REST | 0x00;
    u8Action = ACTION_DOR;
    Outb(FD_DIGITAL_OUTPUT_REGISTER, uDor);
    FD_MOTOR_DELAY;
}

#define CMD_FD_READ     0xE6
#define CMD_FD_WRITE    0xC5
/*read uSize sectors from floppy's uSector to memory's uPhysicalAddr*/
INLINE volatile bool read_floppy(uint_t uSize, uint_t uSector,
    void * uVirtualAddr)
{
    uint8_t u8Driver_head = 0xFF & (uSector >> 16);
    uint8_t u8Track = 0xFF & (uSector >> 8);
    uint8_t u8Sector = 0xFF & uSector;
    ECode ec;
//    kprintf("\ndh:0x%x--track:0x%x--sector:0x%x\n",
//        u8Driver_head, u8Track, u8Sector);
    uint_t uSign;

    u8Action = ACTION_SEEK;

    if (u8Position != u8Track) {
        Outb(FD_DATA_REGISTER, CMD_FD_SEEK);
        FD_CMD_DELAY;
        Outb(FD_DATA_REGISTER, u8Driver_head);
        FD_CMD_DELAY;
        Outb(FD_DATA_REGISTER, u8Track);
//        FD_MOTOR_DELAY;

        ec = peFloppy->TryWait(1000, &uSign);
        peFloppy->Clear();
        assert(NOERROR == ec);
//        kprintf("%d no. 0x%x notify it\n", i, uSign);
        //if (uCount > 10) return false;
    }

    if (u8Position != u8Track) {
        return false;
    }
    //start transfer
    //initial DMA channel
//    kprintf("DMAC status is 0x%x.\n", get_dma_status(0x02));

    DzDmaChannel * pDmaChannel;
    ec = DzRequestDmaChannel(0x0002, &pDmaChannel);

    if (FAILED(ec)) {
        kprintf("RequestDmaChannel Failed!\n");
        return false;
    }
    pDmaChannel->RequestTransfer(DmaTransferMode_DeviceToMemory,
        (void *)uVirtualAddr, uSize * 512);

//    setup_DMA(DMA_MODE_READ, uPhysicalAddr, uSize);

    FD_CMD_DELAY;

//    kprintf("remain:0x%x\n", pDmaChannel->GetTransferResidue());
    //set flag
    u8Action = ACTION_READ;
//kprintf("remain:0x%x\n", get_dma_residue(0x02));
    //read
    Outb(FD_DATA_REGISTER, CMD_FD_READ);
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, u8Driver_head); //0x03&
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, u8Track);
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, u8Driver_head >> 2);
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, u8Sector);
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, 0x02);       //0x02 means 512 bytes per sector
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, 0x50);       //per track 80 sectors
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, 0x03);       //gap = 0x03
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, 0xFF);       //sector size (0xff when n!=0 ?)

//    peFloppy->Wait();
//    kprintf("remain:0x%x\n", pDmaChannel->GetTransferResidue());
    FD_CMD_DELAY;
//    kprintf("remain:0x%x\n", pDmaChannel->GetTransferResidue());

    peFloppy->TryWait(1000, &uSign);
    peFloppy->Clear();

    if (!is_mrq()) {
        kprintf("is_mrq fail");
        pDmaChannel->Release();
        return false;
    }

    if (pDmaChannel->GetTransferResidue() != 0x00) {
        kprintf("remain:0x%x\n", pDmaChannel->GetTransferResidue());
        pDmaChannel->Release();
        return false;
    }

    pDmaChannel->Release();
    return true;
}

INLINE bool write_floppy(uint_t uSize, uint_t uSector, void * uVirtualAddr)
{
    uint8_t u8Driver_head = 0xFF & (uSector >> 16);
    uint8_t u8Track = 0xFF & (uSector >> 8);
    uint8_t u8Sector = 0xFF & uSector;
    ECode ec;

//    kprintf("\ndh:0x%x--track:0x%x--sector:0x%x\n",
//        u8Driver_head, u8Track, u8Sector);
    u8Action = ACTION_SEEK;

    uint_t uCount = 0;
    while (u8Position != u8Track) {
        Outb(FD_DATA_REGISTER, CMD_FD_SEEK);
        FD_CMD_DELAY;
        Outb(FD_DATA_REGISTER, u8Driver_head);
        FD_CMD_DELAY;
        Outb(FD_DATA_REGISTER, u8Track);
//        FD_MOTOR_DELAY;
        uCount++;
        peFloppy->TryWait(1000, NULL);
        peFloppy->Clear();
        if (uCount > 10) {
            kprintf("Seek error: timeout!\n");
            return false;
        }
    }
//    kprintf("Seek successful!\n");
    DzDmaChannel * pDmaChannel;
    ec = DzRequestDmaChannel(0x0002, &pDmaChannel);

    if (FAILED(ec)) {
        kprintf("RequestDmaChannel Failed!\n");
        return false;
    }
    pDmaChannel->RequestTransfer(DmaTransferMode_MemoryToDevice,
        (void *)uVirtualAddr, uSize * 512);
//    setup_DMA(DMA_MODE_WRITE, uPhysicalAddr, uSize);
    FD_CMD_DELAY;
//kprintf("remain:0x%x\n", get_dma_residue(0x02));
    //start transfer

    //set flag
    u8Action = ACTION_WRITE;

    Outb(FD_DATA_REGISTER, CMD_FD_WRITE);
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, u8Driver_head);//0x03&
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, u8Track);
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, u8Driver_head >> 2);
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, u8Sector);
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, 0x02);       //0x02 means 512 bytes per sector
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, 0x50);       //per track 80 sectors
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, 0x03);       //gap = 0x03
    FD_CMD_DELAY;

    Outb(FD_DATA_REGISTER, 0xFF);       //sector size (0xff when n!=0 ?)

    peFloppy->TryWait(1000, NULL);
    peFloppy->Clear();

    if (!is_mrq()) {
        kprintf("is_mrq fail");
        pDmaChannel->Release();
        return false;
    }

    int residue = pDmaChannel->GetTransferResidue();
    kprintf("remain:0x%x\n", residue);
    if (residue != 0) {

        pDmaChannel->Release();
        return false;
    }

//kprintf("remain:0x%x\n", get_dma_residue(0x02));

    pDmaChannel->Release();
    return true;
}

#endif //__ELASTOS_FD_REGISTER_H__
